import static org.junit.Assert.*;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import org.junit.Test;

public class ParserTest {

	@Test
	public void testGeneralUnparsableInputs() {
		// Empty Command
		try {
			Command cmd = Parser.parseCommand("");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("No input!", e.getMessage());
		}

		// Garbage Command
		try {
			Command cmd = Parser.parseCommand("awfawfwa");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Sorry, we could not understand this command.",
					e.getMessage());
		}

		// Space between words after project tag
		try {
			Command cmd = Parser.parseCommand("add task #pro ject");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Attribute tag needed before \"ject\"", e.getMessage());
		}

	}

	@Test
	public void testAddCommands() {
		// Command to open popup
		AddCommand correctCmd = new AddCommand();
		correctCmd.isGuiOpenCommand = true;
		correctCmd.task = null;

		try {
			Command cmd = Parser.parseCommand("add");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// All fields filled
		correctCmd = new AddCommand();
		correctCmd.isGuiOpenCommand = false;
		correctCmd.task = new Task();
		correctCmd.task.id = Task.EMPTY_ID;
		correctCmd.task.title = "Test Task";
		correctCmd.task.place = "LT13";
		correctCmd.task.description = "Description2";
		correctCmd.task.project = "Project";
		try {
			correctCmd.task.startTime = new SimpleDateFormat("dd/MM/yyyy HH:mm")
					.parse("13/08/2014 12:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}
		try {
			correctCmd.task.endTime = new SimpleDateFormat("dd/MM/yyyy HH:mm")
					.parse("14/08/2014 14:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			Command cmd = Parser
					.parseCommand("add Test Task /st 13/08/2014 12:00 /et 14/08/2014 14:00 /pl LT13 /des Description2 #Project");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Missing Title
		try {
			Command cmd = Parser
					.parseCommand("add /st 13/08/2014 12:00 /et 14/08/2014 14:00");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Task's title is missing!", e.getMessage());
		}

		// Add Comamnd's unusable tags

		try {
			Command cmd = Parser
					.parseCommand("add new Task /tit Another Title -d -nd /id 12 /key keyword");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("/tit -d -nd /id /key tag(s) not available for add",
					e.getMessage());
		}

		// Empty field
		try {
			Command cmd = Parser.parseCommand("add new /st");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Missing input after /st", e.getMessage());
		}

		// White spaces between fields
		correctCmd = new AddCommand();
		correctCmd.isGuiOpenCommand = false;
		correctCmd.task = new Task();
		correctCmd.task.id = Task.EMPTY_ID;
		correctCmd.task.title = "Task   With   Whitespaces";
		correctCmd.task.place = "Discussion    Room   4";
		correctCmd.task.description = "Task     Description";
		correctCmd.task.project = "TaskProject";
		try {
			correctCmd.task.startTime = new SimpleDateFormat("dd/MM/yyyy HH:mm")
					.parse("10/07/2014 12:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}
		try {
			correctCmd.task.endTime = new SimpleDateFormat("dd/MM/yyyy HH:mm")
					.parse("11/07/2014 14:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			Command cmd = Parser
					.parseCommand("add    Task   With   Whitespaces   #TaskProject     /st     10/07/2014     12:00      "
							+ "/et     11/07/2014     14:00    /pl    Discussion    Room   4    /des     Task     Description        ");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Repeated tags
		try {
			Command cmd = Parser
					.parseCommand("add Task Title #proj #proj /des descript 1 "
							+ "/des descript 2 /st today /st tomorrow /et today /et tomorrow "
							+ "/pl here /pl there");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Repeating tags: # /des /st /et /pl", e.getMessage());
		}

		// Reversed dates
		try {
			Command cmd = Parser.parseCommand("add new /st 13/4 /et 12/4");
			fail();
		} catch (InvalidInputException e) {
			assertEquals(
					"Error: start time (13/04/2014 00:00) comes after end time (12/04/2014 23:59)",
					e.getMessage());
		}

		// Random Capitalisation
		correctCmd = new AddCommand();
		correctCmd.isGuiOpenCommand = false;
		correctCmd.task = new Task();
		correctCmd.task.id = Task.EMPTY_ID;
		correctCmd.task.title = "CaPs TesT";
		correctCmd.task.place = "At HoMe";
		correctCmd.task.description = "DescriPtion2";
		correctCmd.task.project = "ProJect";
		try {
			correctCmd.task.startTime = new SimpleDateFormat("dd/MM/yyyy HH:mm")
					.parse("11/04/2014 21:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}
		try {
			correctCmd.task.endTime = new SimpleDateFormat("dd/MM/yyyy HH:mm")
					.parse("11/04/2014 21:30");
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			Command cmd = Parser
					.parseCommand("aDd CaPs TesT /ST 11/04/2014 21:00 /Et 11/04/2014 21:30 /Pl At HoMe /Des DescriPtion2 #ProJect");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

	}

	@Test
	public void testEditCommand() {

		// Referring to task by Id, editing through popup
		EditCommand correctCmd = new EditCommand();
		correctCmd.isGuiOpenCommand = true;
		correctCmd.taskId = 15;

		try {
			Command cmd = Parser.parseCommand("edit /id 15");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Referring to task by title, editing through popup
		correctCmd = new EditCommand();
		correctCmd.isGuiOpenCommand = true;
		correctCmd.taskTitle = "A Task Title";

		try {
			Command cmd = Parser.parseCommand("edit A Task Title");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Editing from console, referring to task by title
		correctCmd = new EditCommand();
		correctCmd.taskTitle = "A Task Title";
		correctCmd.newTaskDetails = new Task();

		correctCmd.isEditingPlace = true;
		correctCmd.newTaskDetails.place = "Chocolate Factory";

		correctCmd.isEditingProject = true;
		correctCmd.newTaskDetails.project = "ChocolateMaking";

		correctCmd.isEditingTitle = true;
		correctCmd.newTaskDetails.title = "Make Chocolate";

		correctCmd.isEditingDescription = true;
		correctCmd.newTaskDetails.description = "Can't have enough chocolates";

		try {
			correctCmd.isEditingStartTime = true;
			correctCmd.newTaskDetails.startTime = new SimpleDateFormat(
					"dd/MM/yyyy HH:mm").parse("10/4/2014 2:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			correctCmd.isEditingEndTime = true;
			correctCmd.newTaskDetails.endTime = new SimpleDateFormat(
					"dd/MM/yyyy HH:mm").parse("11/4/2014 2:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			Command cmd = Parser
					.parseCommand("edit A Task Title /tit Make Chocolate /pl Chocolate Factory "
							+ "/st 10/4/2014 2:00 /et 11/4/2014 2:00 #ChocolateMaking "
							+ "/des Can't have enough chocolates");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Editing from console using task id
		correctCmd = new EditCommand();
		correctCmd.taskId = 12;
		correctCmd.newTaskDetails = new Task();

		correctCmd.isEditingPlace = true;
		correctCmd.newTaskDetails.place = "Chocolate Factory";

		correctCmd.isEditingProject = true;
		correctCmd.newTaskDetails.project = "ChocolateMaking";

		correctCmd.isEditingTitle = true;
		correctCmd.newTaskDetails.title = "Make Chocolate";

		correctCmd.isEditingDescription = true;
		correctCmd.newTaskDetails.description = "Can't have enough chocolates";

		try {
			correctCmd.isEditingStartTime = true;
			correctCmd.newTaskDetails.startTime = new SimpleDateFormat(
					"dd/MM/yyyy HH:mm").parse("10/4/2014 2:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			correctCmd.isEditingEndTime = true;
			correctCmd.newTaskDetails.endTime = new SimpleDateFormat(
					"dd/MM/yyyy HH:mm").parse("11/4/2014 2:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			Command cmd = Parser
					.parseCommand("edit /id 12 /tit Make Chocolate /pl Chocolate Factory "
							+ "/st 10/4/2014 2:00 /et 11/4/2014 2:00 #ChocolateMaking "
							+ "/des Can't have enough chocolates");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Edit with both id and title

		try {
			Command cmd = Parser.parseCommand("edit Meeting@6 /id 2 /et 6pm");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Please refer to task by either title or id.",
					e.getMessage());
		}

		// Edit without id and title
		try {
			Command cmd = Parser.parseCommand("edit     /et 6pm");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Please indicate task with id or title.",
					e.getMessage());
		}

		// Unusable tags in edit
		try {
			Command cmd = Parser.parseCommand("edit /id 1 /key Keyword -d -nd");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("/key -d -nd tag(s) not available for edit",
					e.getMessage());
		}

		// Repeated tags
		try {
			Command cmd = Parser
					.parseCommand("edit /id 20 /id 21 /tit title 1 /tit title 2 #proj #proj /des descript 1 "
							+ "/des descript 2 /st today /st tomorrow /et today /et tomorrow "
							+ "/pl here /pl there");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Repeating tags: /id /tit # /des /st /et /pl",
					e.getMessage());
		}

		// Missing Title
		try {
			Command cmd = Parser
					.parseCommand("edit this Task /tit /st 13/08/2014 12:00 /et 14/08/2014 14:00");
			fail();
		} catch (InvalidInputException e) {

			assertEquals("Missing input after /tit", e.getMessage());
		}

		// Invalid id
		try {
			Command cmd = Parser
					.parseCommand("edit /id 12f232 w /st 13/08/2014 12:00 /et 14/08/2014 14:00");
			fail();
		} catch (InvalidInputException e) {

			assertEquals("Invalid Task ID: 12f232 w", e.getMessage());
		}

		// Id 0 or less as invalid
		try {
			Command cmd = Parser.parseCommand("edit /id 0");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Invalid Task ID: 0", e.getMessage());
		}

		// Missing id
		try {
			Command cmd = Parser
					.parseCommand("edit /id /st 13/08/2014 12:00 /et 14/08/2014 14:00");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Missing input after /id", e.getMessage());
		}

		// Reversed Dates
		try {
			Command cmd = Parser
					.parseCommand("edit /id 32 /st 15/08/2014 12:00 /et 14/08/2014 14:00");
			fail();
		} catch (InvalidInputException e) {
			assertEquals(
					"Error: start time (15/08/2014 12:00) comes after end time (14/08/2014 14:00)",
					e.getMessage());
		}

		// Removing original fields
		correctCmd = new EditCommand();
		correctCmd.taskTitle = "Previous Title";

		correctCmd.newTaskDetails = new Task();

		correctCmd.isEditingPlace = true;
		correctCmd.newTaskDetails.place = Task.EMPTY_STRING;

		correctCmd.isEditingProject = true;
		correctCmd.newTaskDetails.project = Task.EMPTY_STRING;

		correctCmd.isEditingDescription = true;
		correctCmd.newTaskDetails.description = Task.EMPTY_STRING;

		correctCmd.isEditingStartTime = true;
		correctCmd.newTaskDetails.startTime = Task.EMPTY_TIME;

		correctCmd.isEditingEndTime = true;
		correctCmd.newTaskDetails.endTime = Task.EMPTY_TIME;

		try {
			Command cmd = Parser
					.parseCommand("edit Previous Title /pl /st  /et # /des");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// White spaces between fields, using title
		correctCmd = new EditCommand();
		correctCmd.taskTitle = "Task   With   Whitespaces";
		correctCmd.newTaskDetails = new Task();

		correctCmd.isEditingPlace = true;
		correctCmd.newTaskDetails.place = "A    Random    Place";

		correctCmd.isEditingProject = true;
		correctCmd.newTaskDetails.project = "TaskProject";

		correctCmd.isEditingTitle = true;
		correctCmd.newTaskDetails.title = "Another     Title";

		correctCmd.isEditingDescription = true;
		correctCmd.newTaskDetails.description = "Task     Description";

		try {
			correctCmd.isEditingStartTime = true;
			correctCmd.newTaskDetails.startTime = new SimpleDateFormat(
					"dd/MM/yyyy HH:mm").parse("10/07/2014 12:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			correctCmd.isEditingEndTime = true;
			correctCmd.newTaskDetails.endTime = new SimpleDateFormat(
					"dd/MM/yyyy HH:mm").parse("11/07/2014 14:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			Command cmd = Parser
					.parseCommand("edit    Task   With   Whitespaces  /tit     Another     Title     #TaskProject    "
							+ " /st     10/07/2014     12:00      /et     11/07/2014     14:00   "
							+ " /pl    A    Random    Place    /des     Task     Description        ");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Editing from console with random capitalisation
		correctCmd = new EditCommand();
		correctCmd.taskId = 10;
		correctCmd.newTaskDetails = new Task();

		correctCmd.isEditingPlace = true;
		correctCmd.newTaskDetails.place = "LiBrary";

		correctCmd.isEditingProject = true;
		correctCmd.newTaskDetails.project = "FiNals";

		correctCmd.isEditingTitle = true;
		correctCmd.newTaskDetails.title = "PReP For EXam";

		correctCmd.isEditingDescription = true;
		correctCmd.newTaskDetails.description = "So StressEd";

		try {
			correctCmd.isEditingStartTime = true;
			correctCmd.newTaskDetails.startTime = new SimpleDateFormat(
					"dd/MM/yyyy HH:mm").parse("15/4/2014 8:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			correctCmd.isEditingEndTime = true;
			correctCmd.newTaskDetails.endTime = new SimpleDateFormat(
					"dd/MM/yyyy HH:mm").parse("16/4/2014 18:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			Command cmd = Parser
					.parseCommand("edIt /iD 10 /tIT PReP For EXam /Pl LiBrary "
							+ "/ST 15/4/2014 8:00 /eT 16/4/2014 18:00 #FiNals "
							+ "/dEs So StressEd");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

	}

	@Test
	public void testListCommand() {

		// list all tasks
		ListCommand correctCmd = new ListCommand();
		correctCmd.isListAll = true;
		try {
			Command cmd = Parser.parseCommand("list");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// list all done tasks (with extra white spaces)

		correctCmd = new ListCommand();
		correctCmd.isListAll = true;
		correctCmd.displayDoneStates = ListCommand.DisplayDoneState.DONE;
		try {
			Command cmd = Parser.parseCommand("list    -d   ");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// list all undone tasks

		correctCmd = new ListCommand();
		correctCmd.isListAll = true;
		correctCmd.displayDoneStates = ListCommand.DisplayDoneState.NOTDONE;
		try {
			Command cmd = Parser.parseCommand("list -nd   ");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// list today
		correctCmd = new ListCommand();
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.HOUR_OF_DAY, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		correctCmd.startTimeFilter = cal.getTime();
		cal.set(Calendar.HOUR_OF_DAY, 23);
		cal.set(Calendar.MINUTE, 59);
		correctCmd.endTimeFilter = cal.getTime();

		try {
			Command cmd = Parser.parseCommand("list  today ");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// list today not done
		correctCmd.displayDoneStates = ListCommand.DisplayDoneState.NOTDONE;
		try {
			Command cmd = Parser.parseCommand("list -nd today ");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Different arrangement + whitespaces
		try {
			Command cmd = Parser.parseCommand("list  today   -nd");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// list filters
		correctCmd = new ListCommand();
		correctCmd.placeFilter = "A place";
		correctCmd.projectFilter = "Project";
		correctCmd.descriptionFilter = "description words";
		correctCmd.titleFilter = "title words";
		try {

			correctCmd.startTimeFilter = new SimpleDateFormat(
					"dd/MM/yyyy HH:mm").parse("15/09/2014 03:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			correctCmd.endTimeFilter = new SimpleDateFormat("dd/MM/yyyy HH:mm")
					.parse("16/09/2014 04:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			Command cmd = Parser
					.parseCommand("list /pl A place #Project /des description words /tit title words /st 15/09/2014 03:00 /et 16/09/2014 04:00");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// list filters done
		correctCmd.displayDoneStates = ListCommand.DisplayDoneState.DONE;
		try {
			Command cmd = Parser
					.parseCommand("list /pl A place #Project /des description words -d /tit title words /st 15/09/2014 03:00 /et 16/09/2014 04:00");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// list key + random whitespaces
		correctCmd = new ListCommand();
		correctCmd.keyWordSearch = "Words to search";
		try {
			Command cmd = Parser
					.parseCommand("list  /key   Words to search   ");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// list key done + random whitespaces
		correctCmd.displayDoneStates = ListCommand.DisplayDoneState.DONE;
		try {
			Command cmd = Parser
					.parseCommand("list   -d  /key   Words to search   ");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// list key not done + random capitalisation
		correctCmd = new ListCommand();
		correctCmd.keyWordSearch = "KeYwOrds";
		correctCmd.displayDoneStates = ListCommand.DisplayDoneState.NOTDONE;
		try {
			Command cmd = Parser.parseCommand("lISt -Nd /kEy KeYwOrds");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// list [date], but with invalid date + random whitespaces

		try {
			Command cmd = Parser.parseCommand("list   random junk  ");
			fail();

		} catch (InvalidInputException e) {
			assertEquals("\"random junk\" is not an understood date",
					e.getMessage());
		}

		// list [date], but with time instead of date + random whitespaces
		try {
			Command cmd = Parser.parseCommand("list   today 2pm  ");
			fail();

		} catch (InvalidInputException e) {
			assertEquals("Please use \"list [day]\" to view tasks for a day",
					e.getMessage());
		}

		// list [date], but with tags behind

		try {
			Command cmd = Parser.parseCommand("list   today /pl a place");
			fail();

		} catch (InvalidInputException e) {
			assertEquals("Please use \"list [day]\" to view tasks for a day",
					e.getMessage());
		}

		// list /key tag with /st /et /tit # /des tags

		try {
			Command cmd = Parser
					.parseCommand("list #Project  /st fri /et sat /des description /key keyword /pl place /tit title");
			fail();

		} catch (InvalidInputException e) {
			assertEquals(
					"/key should be used seperately from /tit /st /et /pl /des #",
					e.getMessage());
		}

		// list /key tag with #
		try {
			Command cmd = Parser.parseCommand("list #Project  /key keyword ");
			fail();

		} catch (InvalidInputException e) {
			assertEquals(
					"/key should be used seperately from /tit /st /et /pl /des #",
					e.getMessage());
		}

		// list /key tag with /st
		try {
			Command cmd = Parser.parseCommand("list /st fri /key keyword");
			fail();

		} catch (InvalidInputException e) {
			assertEquals(
					"/key should be used seperately from /tit /st /et /pl /des #",
					e.getMessage());
		}

		// list /key tag with /et
		try {
			Command cmd = Parser.parseCommand("list /et fri /key keyword");
			fail();

		} catch (InvalidInputException e) {
			assertEquals(
					"/key should be used seperately from /tit /st /et /pl /des #",
					e.getMessage());
		}

		// list /key tag with /pl
		try {
			Command cmd = Parser.parseCommand("list /pl place /key keyword");
			fail();

		} catch (InvalidInputException e) {
			assertEquals(
					"/key should be used seperately from /tit /st /et /pl /des #",
					e.getMessage());
		}

		// list /key tag with /des
		try {
			Command cmd = Parser
					.parseCommand("list /key keyword /des description");
			fail();

		} catch (InvalidInputException e) {
			assertEquals(
					"/key should be used seperately from /tit /st /et /pl /des #",
					e.getMessage());
		}

		// list /key tag with /tit
		try {
			Command cmd = Parser.parseCommand("list /key keyword /tit title");
			fail();

		} catch (InvalidInputException e) {
			assertEquals(
					"/key should be used seperately from /tit /st /et /pl /des #",
					e.getMessage());
		}

		// done and not done tags present together

		try {
			Command cmd = Parser.parseCommand("list -nd /key keyword -d");
			fail();

		} catch (InvalidInputException e) {
			assertEquals("Only one of these can be present: -d, -nd",
					e.getMessage());
		}

		// Unusable tags in list
		try {
			Command cmd = Parser.parseCommand("list /id 1 /tit Title Filter");
			fail();

		} catch (InvalidInputException e) {
			assertEquals("/id tag(s) not available for list", e.getMessage());
		}

		// reversed dates
		try {
			Command cmd = Parser
					.parseCommand("list #Project /st 13/4 -d /et 12/4");
			fail();
		} catch (InvalidInputException e) {
			assertEquals(
					"Error: start time (13/04/2014 00:00) comes after end time (12/04/2014 23:59)",
					e.getMessage());
		}

		// empty field /st
		try {
			Command cmd = Parser.parseCommand("list #Project /st    /et 12/4");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Missing input after /st", e.getMessage());
		}

		// White space between filter fields

		correctCmd = new ListCommand();
		correctCmd.placeFilter = "A   Pl ace";
		correctCmd.projectFilter = "Project";
		correctCmd.descriptionFilter = "descri   ption wo rds";
		correctCmd.titleFilter = "tit   le word  s";
		try {

			correctCmd.startTimeFilter = new SimpleDateFormat(
					"dd/MM/yyyy HH:mm").parse("15/09/2014 03:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			correctCmd.endTimeFilter = new SimpleDateFormat("dd/MM/yyyy HH:mm")
					.parse("16/09/2014 04:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			Command cmd = Parser
					.parseCommand("list     /pl A   Pl ace     #Project     /des descri   ption wo rds    "
							+ "/tit tit   le word  s     /st   15/09/2014    03:00 "
							+ "/et   16/09/2014   04:00 ");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Filters with random capitalisation
		correctCmd = new ListCommand();
		correctCmd.placeFilter = "A Place";
		correctCmd.projectFilter = "ProjEct";
		correctCmd.descriptionFilter = "desCription Words";
		correctCmd.titleFilter = "tiTle wOrds";
		correctCmd.displayDoneStates = ListCommand.DisplayDoneState.DONE;
		try {

			correctCmd.startTimeFilter = new SimpleDateFormat(
					"dd/MM/yyyy HH:mm").parse("15/09/2014 03:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			correctCmd.endTimeFilter = new SimpleDateFormat("dd/MM/yyyy HH:mm")
					.parse("16/09/2014 04:00");
		} catch (ParseException e) {
			e.printStackTrace();
		}

		try {
			Command cmd = Parser
					.parseCommand("LiSt /pL A Place #ProjEct /Des desCription Words "
							+ "/tIt tiTle wOrds /St 15/09/2014 03:00 "
							+ "/eT 16/09/2014 04:00 -D");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Repeated tags
		try {
			Command cmd = Parser
					.parseCommand("list -d -d -nd -nd #proj #proj /des descript 1 "
							+ "/des descript 2 /st today /st tomorrow /et today /et tomorrow "
							+ "/pl here /pl there /tit Title 1 /tit Title 2");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Repeating tags: -d -nd # /des /st /et /pl /tit",
					e.getMessage());
		}

	}

	@Test
	public void testDeleteCommand() {
		// Delete with id
		DeleteCommand correctCmd = new DeleteCommand();
		correctCmd.taskId = 1;
		try {
			Command cmd = Parser.parseCommand("delete /id 1");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Delete with title
		correctCmd = new DeleteCommand();
		correctCmd.taskTitle = "Task Title To Delete";
		try {
			Command cmd = Parser.parseCommand("delete Task Title To Delete");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Unusable Tags in delete
		try {
			Command cmd = Parser
					.parseCommand("delete /id 1 /tit Title -d -nd /st 13/4/2014 /et 14/4/2014 /pl Place "
							+ "/des Description /key keyword #Project");
			fail();
		} catch (InvalidInputException e) {
			assertEquals(
					"/tit -d -nd /st /et /pl /des /key # tag(s) not available for delete",
					e.getMessage());
		}

		// Empty id
		try {
			Command cmd = Parser.parseCommand("delete /id");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Missing input after /id", e.getMessage());
		}

		// Id and title used together
		try {
			Command cmd = Parser.parseCommand("delete Maths Exam  /id 2");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Please refer to task by either title or id.",
					e.getMessage());
		}

		// Invalid id
		try {
			Command cmd = Parser.parseCommand("delete /id 32cddw3 23d");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Invalid Task ID: 32cddw3 23d", e.getMessage());
		}

		// Id 0 or less as invalid
		try {
			Command cmd = Parser.parseCommand("delete /id 0");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Invalid Task ID: 0", e.getMessage());
		}

		// Commands with lots of whitespaces
		correctCmd = new DeleteCommand();
		correctCmd.taskId = 21;
		try {
			Command cmd = Parser.parseCommand("delete     /id      21");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}
		correctCmd = new DeleteCommand();
		correctCmd.taskTitle = "Far    far   away";
		try {
			Command cmd = Parser.parseCommand("delete     Far    far   away");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// With random capitalisation
		correctCmd = new DeleteCommand();
		correctCmd.taskId = 2;
		try {
			Command cmd = Parser.parseCommand("dElEte /Id 2");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Repeated id tag
		try {
			Command cmd = Parser.parseCommand("delete /id 1 /id 2");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Repeating tags: /id", e.getMessage());
		}

	}

	@Test
	public void testViewCommand() {
		// View with id
		ViewCommand correctCmd = new ViewCommand();
		correctCmd.taskId = 12;
		try {
			Command cmd = Parser.parseCommand("view /id 12");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// View with title
		correctCmd = new ViewCommand();
		correctCmd.taskTitle = "Do Homework";
		try {
			Command cmd = Parser.parseCommand("view Do Homework");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Unusable tags in view
		try {
			Command cmd = Parser
					.parseCommand("view /id 1 /tit Title -d -nd /st 13/4/2014 /et 14/4/2014 /pl Place "
							+ "/des Description /key keyword #Project");
			fail();
		} catch (InvalidInputException e) {
			assertEquals(
					"/tit -d -nd /st /et /pl /des /key # tag(s) not available for view",
					e.getMessage());
		}

		// Empty id
		try {
			Command cmd = Parser.parseCommand("view /id  ");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Missing input after /id", e.getMessage());
		}

		// Id and title used together
		try {
			Command cmd = Parser.parseCommand("view Project Meeting /id 2");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Please refer to task by either title or id.",
					e.getMessage());
		}

		// Invalid id
		try {
			Command cmd = Parser.parseCommand("view /id 888 dwq");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Invalid Task ID: 888 dwq", e.getMessage());
		}

		// Id 0 or less as invalid
		try {
			Command cmd = Parser.parseCommand("view /id -1");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Invalid Task ID: -1", e.getMessage());
		}

		// Commands with lots of whitespaces
		correctCmd = new ViewCommand();
		correctCmd.taskId = 111;
		try {
			Command cmd = Parser.parseCommand("view      /id       111");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}
		correctCmd = new ViewCommand();
		correctCmd.taskTitle = "buy    new   books";
		try {
			Command cmd = Parser.parseCommand("view     buy    new   books");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Random Capitalisation
		correctCmd = new ViewCommand();
		correctCmd.taskId = 11;
		try {
			Command cmd = Parser.parseCommand("viEw /iD 11");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Repeated id tag
		try {
			Command cmd = Parser.parseCommand("view /id 1 /id 2");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Repeating tags: /id", e.getMessage());
		}
	}

	@Test
	public void testDoneCommand() {
		// Done with id
		DoneCommand correctCmd = new DoneCommand();
		correctCmd.taskId = 11;
		correctCmd.isDone = true;
		try {
			Command cmd = Parser.parseCommand("done /id 11");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Done with title
		correctCmd = new DoneCommand();
		correctCmd.taskTitle = "Write Thesis";
		correctCmd.isDone = true;
		try {
			Command cmd = Parser.parseCommand("done Write Thesis");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Done with unusable tags
		try {
			Command cmd = Parser
					.parseCommand("done /id 1 /tit Title -d -nd /st 13/4/2014 /et 14/4/2014 /pl Place "
							+ "/des Description /key keyword #Project");
			fail();
		} catch (InvalidInputException e) {
			assertEquals(
					"/tit -d -nd /st /et /pl /des /key # tag(s) not available for done",
					e.getMessage());
		}

		// Done with empty id
		try {
			Command cmd = Parser.parseCommand("done /id  ");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Missing input after /id", e.getMessage());
		}

		// Done with id and title used together
		try {
			Command cmd = Parser.parseCommand("done Tutorial Homework /id 2");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Please refer to task by either title or id.",
					e.getMessage());
		}

		// Done with invalid id
		try {
			Command cmd = Parser.parseCommand("done /id abc");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Invalid Task ID: abc", e.getMessage());
		}

		// Done id 0 or less as invalid
		try {
			Command cmd = Parser.parseCommand("done /id 0");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Invalid Task ID: 0", e.getMessage());
		}

		// Done commands with lots of whitespaces
		correctCmd = new DoneCommand();
		correctCmd.taskId = 64;
		correctCmd.isDone = true;
		try {
			Command cmd = Parser.parseCommand("done      /id       64");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}
		correctCmd = new DoneCommand();
		correctCmd.taskTitle = "Studying   Physics   101";
		correctCmd.isDone = true;
		try {
			Command cmd = Parser
					.parseCommand("done     Studying   Physics   101   ");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Done commands with random capitalisation
		correctCmd = new DoneCommand();
		correctCmd.taskId = 6;
		correctCmd.isDone = true;
		try {
			Command cmd = Parser.parseCommand("donE /Id 6");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Done with repeated id tag
		try {
			Command cmd = Parser.parseCommand("done /id 1 /id 2");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Repeating tags: /id", e.getMessage());
		}

	}

	@Test
	public void testNotdoneCommand() {
		// Notdone with id
		DoneCommand correctCmd = new DoneCommand();
		correctCmd.taskId = 33;
		correctCmd.isDone = false;
		try {
			Command cmd = Parser.parseCommand("notdone /id 33");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Notdone with title
		correctCmd = new DoneCommand();
		correctCmd.taskTitle = "Build 3D Model";
		correctCmd.isDone = false;
		try {
			Command cmd = Parser.parseCommand("notdone Build 3D Model");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Notdone with unusable tags
		try {
			Command cmd = Parser
					.parseCommand("notdone /id 1 /tit Title -d -nd /st 13/4/2014 /et 14/4/2014 /pl Place "
							+ "/des Description /key keyword #Project");
			fail();
		} catch (InvalidInputException e) {
			assertEquals(
					"/tit -d -nd /st /et /pl /des /key # tag(s) not available for notdone",
					e.getMessage());
		}

		// Notdone with empty id
		try {
			Command cmd = Parser.parseCommand("notdone /id");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Missing input after /id", e.getMessage());
		}

		// Notdone with both id and title
		try {
			Command cmd = Parser.parseCommand("notdone Clean the room /id 4");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Please refer to task by either title or id.",
					e.getMessage());
		}

		// Notdone with invalid id
		try {
			Command cmd = Parser.parseCommand("notdone /id z3421");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Invalid Task ID: z3421", e.getMessage());
		}

		// Notdone id 0 or less as invalid
		try {
			Command cmd = Parser.parseCommand("notdone /id 0");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Invalid Task ID: 0", e.getMessage());
		}

		// Notdone commands with lots of whitespaces
		correctCmd = new DoneCommand();
		correctCmd.taskId = 8;
		correctCmd.isDone = false;
		try {
			Command cmd = Parser.parseCommand("notdone      /id       8   ");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}
		correctCmd = new DoneCommand();
		correctCmd.taskTitle = "Find   New   House";
		correctCmd.isDone = false;
		try {
			Command cmd = Parser
					.parseCommand("notdone    Find   New   House   ");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Notdone command with random capitalisation
		correctCmd = new DoneCommand();
		correctCmd.taskId = 8;
		correctCmd.isDone = false;
		try {
			Command cmd = Parser.parseCommand("notdonE /iD 8");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// Notdone with repeated id tag
		try {
			Command cmd = Parser.parseCommand("notdone /id 1 /id 2");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("Repeating tags: /id", e.getMessage());
		}
	}

	@Test
	public void testSearchboxCommand() {
		// Searchbox command
		SearchboxCommand correctCmd = new SearchboxCommand();
		try {
			Command cmd = Parser.parseCommand("searchbox");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// With tag at the back
		try {
			Command cmd = Parser.parseCommand("searchbox /id 21");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("\"searchbox\" should not be followed by details",
					e.getMessage());
		}

		// With extra whitespaces
		try {
			Command cmd = Parser.parseCommand("    searchbox         ");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// With random capitalisation
		try {
			Command cmd = Parser.parseCommand("SeARchBox");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}
	}

	@Test
	public void testUndoCommand() {
		// Undo command
		UndoCommand correctCmd = new UndoCommand();
		try {
			Command cmd = Parser.parseCommand("undo");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// With tag at the back
		try {
			Command cmd = Parser.parseCommand("undo /tit title");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("\"undo\" should not be followed by details",
					e.getMessage());
		}

		// With extra whitespaces
		try {
			Command cmd = Parser.parseCommand("    undo     ");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// With random capitalisation
		try {
			Command cmd = Parser.parseCommand("UnDo");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}
	}

	@Test
	public void testRedoCommand() {
		// Redo command
		RedoCommand correctCmd = new RedoCommand();
		try {
			Command cmd = Parser.parseCommand("redo");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// With tag at the back
		try {
			Command cmd = Parser.parseCommand("redo /st 29/12/2013 3:00");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("\"redo\" should not be followed by details",
					e.getMessage());
		}

		// With extra whitespaces
		try {
			Command cmd = Parser.parseCommand("    redo     ");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// With random capitalisation
		try {
			Command cmd = Parser.parseCommand("REDo");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}
	}

	@Test
	public void testExitCommand() {
		// Exit command
		ExitCommand correctCmd = new ExitCommand();
		try {
			Command cmd = Parser.parseCommand("exit");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// With tag at the back
		try {
			Command cmd = Parser.parseCommand("exit #ThisProgram");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("\"exit\" should not be followed by details",
					e.getMessage());
		}

		// With extra whitespaces
		try {
			Command cmd = Parser.parseCommand("       exit     ");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// With random capitalisation
		try {
			Command cmd = Parser.parseCommand("eXit");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}
	}

	@Test
	public void testHelpCommand() {
		// Help command
		HelpCommand correctCmd = new HelpCommand();
		try {
			Command cmd = Parser.parseCommand("help");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// With tag at the back
		try {
			Command cmd = Parser.parseCommand("help /pl Hospital");
			fail();
		} catch (InvalidInputException e) {
			assertEquals("\"help\" should not be followed by details",
					e.getMessage());
		}

		// With extra whitespaces
		try {
			Command cmd = Parser.parseCommand("     help     ");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}

		// With random capitalisation
		try {
			Command cmd = Parser.parseCommand("HeLp");
			assertTrue(isSameCommand(correctCmd, cmd));
		} catch (InvalidInputException e) {
			fail();
		}
	}

	@Test
	public void testDates() {

		// Normal date as start time
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.DAY_OF_MONTH, 12);
		cal.set(Calendar.MONTH, Calendar.JANUARY);
		cal.set(Calendar.YEAR, 2015);
		cal.set(Calendar.HOUR_OF_DAY, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		try {
			Date date = Parser.parseTime("12/01/2015", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Normal date as end time
		cal.set(Calendar.HOUR_OF_DAY, 23);
		cal.set(Calendar.MINUTE, 59);
		try {
			Date date = Parser.parseTime("12/01/2015", false);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Date without year
		cal.set(Calendar.DAY_OF_MONTH, 12);
		cal.set(Calendar.MONTH, Calendar.FEBRUARY);
		cal.set(Calendar.HOUR_OF_DAY, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		cal.set(Calendar.YEAR, Calendar.getInstance().get(Calendar.YEAR));
		try {
			Date date = Parser.parseTime("12/02", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Abbreviated year
		cal = Calendar.getInstance();
		cal.set(Calendar.DAY_OF_MONTH, 12);
		cal.set(Calendar.MONTH, Calendar.MARCH);
		cal.set(Calendar.YEAR, 2015);
		cal.set(Calendar.HOUR_OF_DAY, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		try {
			Date date = Parser.parseTime("12/03/15", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Sunday with random capitalisation
		cal.setFirstDayOfWeek(Calendar.SUNDAY);
		cal = Calendar.getInstance();
		cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
		cal.set(Calendar.HOUR_OF_DAY, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		try {
			Date date = Parser.parseTime("suNday", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}
		try {
			Date date = Parser.parseTime("sUn", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Monday with random capitalisation
		cal.add(Calendar.DATE, 1);
		try {
			Date date = Parser.parseTime("monDay", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}
		try {
			Date date = Parser.parseTime("mOn", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Tuesday with random capitalisation
		cal.add(Calendar.DATE, 1);
		try {
			Date date = Parser.parseTime("TuesDay", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}
		try {
			Date date = Parser.parseTime("TUe", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Wednesday with random capitalisation
		cal.add(Calendar.DATE, 1);
		try {
			Date date = Parser.parseTime("WedNesDay", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}
		try {
			Date date = Parser.parseTime("WeD", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Thursday with random capitalisation
		cal.add(Calendar.DATE, 1);
		try {
			Date date = Parser.parseTime("ThuRsDay", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}
		try {
			Date date = Parser.parseTime("thU", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Friday with random capitalisation
		cal.add(Calendar.DATE, 1);
		try {
			Date date = Parser.parseTime("FridAy", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}
		try {
			Date date = Parser.parseTime("fri", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Saturday with random capitalisation
		cal.add(Calendar.DATE, 1);
		try {
			Date date = Parser.parseTime("SatuRDay", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}
		try {
			Date date = Parser.parseTime("Sat", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Next Sunday with random capitalisation
		cal.add(Calendar.DATE, 1);
		try {
			Date date = Parser.parseTime("nxt Sunday", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}
		try {
			Date date = Parser.parseTime("next Sun", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Next Monday with random capitalisation
		cal.add(Calendar.DATE, 1);
		try {
			Date date = Parser.parseTime("next MondAy", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}
		try {
			Date date = Parser.parseTime("nxt MOn", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Next Tuesday with random capitalisation
		cal.add(Calendar.DATE, 1);
		try {
			Date date = Parser.parseTime("nxt Tuesday", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}
		try {
			Date date = Parser.parseTime("next tuE", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Next Wednesday with random capitalisation
		cal.add(Calendar.DATE, 1);
		try {
			Date date = Parser.parseTime("next WednesdaY", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}
		try {
			Date date = Parser.parseTime("nxt Wed", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Next Thursday with random capitalisation
		cal.add(Calendar.DATE, 1);
		try {
			Date date = Parser.parseTime("nxt thURSday", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}
		try {
			Date date = Parser.parseTime("next thu", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Next Friday with random capitalisation
		cal.add(Calendar.DATE, 1);
		try {
			Date date = Parser.parseTime("next fRidAy", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}
		try {
			Date date = Parser.parseTime("nxt frI", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Next Saturday with random capitalisation
		cal.add(Calendar.DATE, 1);
		try {
			Date date = Parser.parseTime("nExt SatURDAY", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}
		try {
			Date date = Parser.parseTime("nXt Sat", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Today with random capitalisation
		cal = Calendar.getInstance();
		cal.set(Calendar.HOUR_OF_DAY, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		try {
			Date date = Parser.parseTime("TodAY", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Tomorrow with random capitalisation
		cal.add(Calendar.DATE, 1);
		try {
			Date date = Parser.parseTime("TomoRRow", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

	}

	@Test
	public void testTimes() {
		// 12-hour (without minutes)
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.HOUR_OF_DAY, 12);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		try {
			Date date = Parser.parseTime("12pm", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}
		// With Spaces
		try {
			Date date = Parser.parseTime("  12   pm  ", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// 12-hour (with minutes)
		cal.set(Calendar.HOUR_OF_DAY, 1);
		cal.set(Calendar.MINUTE, 10);
		try {
			Date date = Parser.parseTime("1:10am", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}
		// With Spaces
		try {
			Date date = Parser.parseTime("  1:10   am  ", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// 24-hour
		cal.set(Calendar.HOUR_OF_DAY, 16);
		cal.set(Calendar.MINUTE, 49);
		try {
			Date date = Parser.parseTime("16:49", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

	}

	@Test
	public void testDateTimeCombinations() {
		// 2 Words date then time (with extra whitespaces)
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.HOUR_OF_DAY, 22);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		try {
			Date date = Parser.parseTime("  today   10pm  ", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}
		// 2 Words time then date (with extra whitespaces)
		try {
			Date date = Parser.parseTime("  10pm   today  ", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Unparsable in one word out of two
		try {
			Date date = Parser.parseTime("abc 10pm", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			assertEquals("\"abc 10pm\" is not an understood time format",
					e.getMessage());
		}

		// 3 words 2-worded-date then time (with extra whitespaces)
		cal.setFirstDayOfWeek(Calendar.SUNDAY);
		cal = Calendar.getInstance();
		cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
		cal.set(Calendar.HOUR_OF_DAY, 3);
		cal.set(Calendar.MINUTE, 45);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		cal.add(Calendar.DATE, 7);
		try {
			Date date = Parser.parseTime("  nxt sun  3:45am ", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// 3 words time then 2-worded-date (with extra whitespaces)
		try {
			Date date = Parser.parseTime("  3:45am  nxt  sun ", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// 3 words 2-worded-time then date (with extra whitespaces)
		cal.setFirstDayOfWeek(Calendar.SUNDAY);
		cal = Calendar.getInstance();
		cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
		cal.set(Calendar.HOUR_OF_DAY, 14);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		try {
			Date date = Parser.parseTime(" 2 pm mon", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// 3 words date then 2-worded-time (with extra whitespaces)
		try {
			Date date = Parser.parseTime(" mon   2  pm", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Unparsable in one word out of three
		try {
			Date date = Parser.parseTime("10 pm xyz", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			assertEquals("\"10 pm xyz\" is not an understood time format",
					e.getMessage());
		}

		// 4 words date then time
		cal.setFirstDayOfWeek(Calendar.SUNDAY);
		cal = Calendar.getInstance();
		cal.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY);
		cal.set(Calendar.HOUR_OF_DAY, 16);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		cal.add(Calendar.DATE, 7);
		try {
			Date date = Parser.parseTime("  nxt tue  4 pm ", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// 4 words time then date
		try {
			Date date = Parser.parseTime("  4 pm nxt  tue ", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			fail();
		}

		// Unparsable in one word out of four
		try {
			Date date = Parser.parseTime("10 pm next qwerty", true);
			assertTrue(isSameTime(cal.getTime(), date));
		} catch (InvalidInputException e) {
			assertEquals(
					"\"10 pm next qwerty\" is not an understood time format",
					e.getMessage());
		}

	}

	private boolean isSameCommand(Command commandToMatch, Command commandToCheck) {

		if (commandToMatch instanceof AddCommand) {
			return isMatchingAddCommand(commandToMatch, commandToCheck);
		}

		if (commandToMatch instanceof EditCommand) {
			return isMatchingEditCommand(commandToMatch, commandToCheck);
		}

		if (commandToMatch instanceof ListCommand) {
			return isMatchingListCommand(commandToMatch, commandToCheck);
		}

		if (commandToMatch instanceof DeleteCommand) {
			return isMatchingDeleteCommand(commandToMatch, commandToCheck);
		}

		if (commandToMatch instanceof ViewCommand) {
			return isMatchingViewCommand(commandToMatch, commandToCheck);
		}

		if (commandToMatch instanceof DoneCommand) {
			return isMatchingDoneCommand(commandToMatch, commandToCheck);
		}

		if (commandToMatch instanceof SearchboxCommand) {
			return isMatchingSearchboxCommand(commandToMatch, commandToCheck);
		}

		if (commandToMatch instanceof SearchboxCommand) {
			return isMatchingSearchboxCommand(commandToMatch, commandToCheck);
		}

		if (commandToMatch instanceof UndoCommand) {
			return isMatchingUndoCommand(commandToMatch, commandToCheck);
		}

		if (commandToMatch instanceof RedoCommand) {
			return isMatchingRedoCommand(commandToMatch, commandToCheck);
		}

		if (commandToMatch instanceof ExitCommand) {
			return isMatchingExitCommand(commandToMatch, commandToCheck);
		}

		if (commandToMatch instanceof HelpCommand) {
			return isMatchingHelpCommand(commandToMatch, commandToCheck);
		}

		return false;

	}

	private boolean isMatchingAddCommand(Command commandToMatch,
			Command commandToCheck) {

		if (!(commandToCheck instanceof AddCommand)) {
			return false;
		}

		if (((AddCommand) commandToMatch).isGuiOpenCommand != ((AddCommand) commandToCheck).isGuiOpenCommand) {
			return false;
		}

		if (!isSameExactTask(((AddCommand) commandToMatch).task,
				((AddCommand) commandToCheck).task)) {
			return false;
		}

		return true;
	}

	private boolean isMatchingEditCommand(Command commandToMatch,
			Command commandToCheck) {
		if (!(commandToCheck instanceof EditCommand)) {
			return false;
		}

		if (((EditCommand) commandToMatch).isGuiOpenCommand != ((EditCommand) commandToCheck).isGuiOpenCommand) {
			return false;
		}

		if (!isSameEditFlags((EditCommand) commandToMatch,
				(EditCommand) commandToCheck)) {
			return false;
		}

		if (!isSameAndCorrectEditTarget((EditCommand) commandToMatch,
				(EditCommand) commandToCheck)) {
			return false;
		}

		if (!isSameExactTask(((EditCommand) commandToMatch).newTaskDetails,
				((EditCommand) commandToCheck).newTaskDetails)) {
			return false;
		}

		return true;
	}

	private boolean isSameEditFlags(EditCommand commandToMatch,
			EditCommand commandToCheck) {

		return commandToMatch.isEditingTitle == commandToCheck.isEditingTitle
				&& commandToMatch.isEditingPlace == commandToCheck.isEditingPlace
				&& commandToMatch.isEditingStartTime == commandToCheck.isEditingStartTime
				&& commandToMatch.isEditingEndTime == commandToCheck.isEditingEndTime
				&& commandToMatch.isEditingProject == commandToCheck.isEditingProject
				&& commandToMatch.isEditingDescription == commandToCheck.isEditingDescription;
	}

	private boolean isSameAndCorrectEditTarget(EditCommand commandToMatch,
			EditCommand commandToCheck) {
		if (!isEitherByTitleOrById(commandToMatch, commandToCheck)) {
			return false;
		}

		if (commandToMatch.taskTitle == Task.EMPTY_STRING) {
			return commandToMatch.taskId == commandToCheck.taskId;
		} else {
			return commandToMatch.taskTitle.equals(commandToCheck.taskTitle);
		}
	}

	private boolean isEitherByTitleOrById(EditCommand commandToMatch,
			EditCommand commandToCheck) {
		if (commandToMatch.taskId == Task.EMPTY_ID
				&& commandToMatch.taskTitle == Task.EMPTY_STRING) {
			return false;
		}

		if (commandToMatch.taskId != Task.EMPTY_ID
				&& commandToMatch.taskTitle != Task.EMPTY_STRING) {
			return false;
		}

		if (commandToCheck.taskId == Task.EMPTY_ID
				&& commandToCheck.taskTitle == Task.EMPTY_STRING) {
			return false;
		}

		if (commandToCheck.taskId != Task.EMPTY_ID
				&& commandToCheck.taskTitle != Task.EMPTY_STRING) {
			return false;
		}

		return true;
	}

	private boolean isSameExactTask(Task taskToMatch, Task taskToCheck) {
		boolean isSame = true;
		isSame = isSame && isPassingNullCheck(taskToMatch, taskToCheck);

		if (isEitherTaskNull(taskToMatch, taskToCheck)) {
			return isSame;
		}

		isSame = isSame && isSameId(taskToMatch.id, taskToCheck.id);

		isSame = isSame && isSameString(taskToMatch.title, taskToCheck.title);

		isSame = isSame && isSameString(taskToMatch.place, taskToCheck.place);

		isSame = isSame
				&& isSameString(taskToMatch.description,
						taskToCheck.description);

		isSame = isSame
				&& isSameString(taskToMatch.project, taskToCheck.project);

		isSame = isSame
				&& isSameTime(taskToMatch.startTime, taskToCheck.startTime);

		isSame = isSame && isSameTime(taskToMatch.endTime, taskToCheck.endTime);

		isSame = isSame && (taskToMatch.isDone == taskToCheck.isDone);

		return isSame;
	}

	private boolean isPassingNullCheck(Task taskToMatch, Task taskToCheck) {
		if (taskToMatch == null) {
			if (taskToCheck != null) {
				return false;
			} else {
				return true;
			}
		} else {
			if (taskToCheck == null) {
				return false;
			}
		}
		return true;
	}

	private boolean isEitherTaskNull(Task taskToMatch, Task taskToCheck) {
		return taskToMatch == null || taskToCheck == null;
	}

	private boolean isSameId(int idToMatch, int idToCheck) {
		if (idToMatch != idToCheck) {
			return false;
		}
		return true;
	}

	private boolean isMatchingListCommand(Command commandToMatch,
			Command commandToCheck) {
		if (!(commandToCheck instanceof ListCommand)) {
			return false;
		}

		if (!isSameDetails((ListCommand) commandToMatch,
				(ListCommand) commandToCheck)) {
			return false;
		}

		return true;
	}

	private boolean isSameDetails(ListCommand commandToMatch,
			ListCommand commandToCheck) {
		boolean isSame = true;

		isSame = isSame
				&& isSameString(commandToMatch.titleFilter,
						commandToCheck.titleFilter);
		isSame = isSame
				&& isSameString(commandToMatch.placeFilter,
						commandToCheck.placeFilter);
		isSame = isSame
				&& isSameString(commandToMatch.projectFilter,
						commandToCheck.projectFilter);
		isSame = isSame
				&& isSameString(commandToMatch.descriptionFilter,
						commandToCheck.descriptionFilter);
		isSame = isSame
				&& isSameTime(commandToMatch.startTimeFilter,
						commandToCheck.startTimeFilter);
		isSame = isSame
				&& isSameTime(commandToMatch.endTimeFilter,
						commandToCheck.endTimeFilter);

		isSame = isSame
				&& (commandToMatch.isListAll == commandToCheck.isListAll);

		isSame = isSame
				&& isSameString(commandToMatch.keyWordSearch,
						commandToCheck.keyWordSearch);

		isSame = isSame && isSameDoneState(commandToMatch, commandToCheck);

		return isSame;
	}

	private boolean isSameDoneState(ListCommand commandToMatch,
			ListCommand commandToCheck) {

		return commandToMatch.displayDoneStates == commandToCheck.displayDoneStates;
	}

	private boolean isMatchingDeleteCommand(Command commandToMatch,
			Command commandToCheck) {
		if (!(commandToCheck instanceof DeleteCommand)) {
			return false;
		}

		if (!isSameId(((DeleteCommand) commandToMatch).taskId,
				((DeleteCommand) commandToCheck).taskId)) {
			return false;
		}

		if (!isSameString(((DeleteCommand) commandToMatch).taskTitle,
				((DeleteCommand) commandToCheck).taskTitle)) {
			return false;
		}

		return true;
	}

	private boolean isMatchingViewCommand(Command commandToMatch,
			Command commandToCheck) {
		if (!(commandToCheck instanceof ViewCommand)) {
			return false;
		}

		if (!isSameId(((ViewCommand) commandToMatch).taskId,
				((ViewCommand) commandToCheck).taskId)) {
			return false;
		}

		if (!isSameString(((ViewCommand) commandToMatch).taskTitle,
				((ViewCommand) commandToCheck).taskTitle)) {
			return false;
		}

		return true;
	}

	private boolean isMatchingDoneCommand(Command commandToMatch,
			Command commandToCheck) {
		if (!(commandToCheck instanceof DoneCommand)) {
			return false;
		}

		if (!isSameId(((DoneCommand) commandToMatch).taskId,
				((DoneCommand) commandToCheck).taskId)) {
			return false;
		}

		if (!isSameString(((DoneCommand) commandToMatch).taskTitle,
				((DoneCommand) commandToCheck).taskTitle)) {
			return false;
		}

		if (((DoneCommand) commandToMatch).isDone != ((DoneCommand) commandToCheck).isDone) {
			return false;
		}

		return true;
	}

	private boolean isMatchingSearchboxCommand(Command commandToMatch,
			Command commandToCheck) {
		if (!(commandToCheck instanceof SearchboxCommand)) {
			return false;
		}

		return true;
	}

	private boolean isMatchingUndoCommand(Command commandToMatch,
			Command commandToCheck) {
		if (!(commandToCheck instanceof UndoCommand)) {
			return false;
		}
		return true;
	}

	private boolean isMatchingRedoCommand(Command commandToMatch,
			Command commandToCheck) {
		if (!(commandToCheck instanceof RedoCommand)) {
			return false;
		}
		return true;
	}

	private boolean isMatchingExitCommand(Command commandToMatch,
			Command commandToCheck) {
		if (!(commandToCheck instanceof ExitCommand)) {
			return false;
		}
		return true;
	}

	private boolean isMatchingHelpCommand(Command commandToMatch,
			Command commandToCheck) {
		if (!(commandToCheck instanceof HelpCommand)) {
			return false;
		}
		return true;
	}

	private boolean isSameString(String stringToMatch, String stringToCheck) {
		if (stringToMatch != Task.EMPTY_STRING) {
			if (stringToCheck == Task.EMPTY_STRING) {
				return false;
			}
			if (!stringToMatch.equals(stringToCheck)) {
				return false;
			}
		} else {
			if (stringToCheck != Task.EMPTY_STRING) {
				return false;
			}
		}
		return true;
	}

	private boolean isSameTime(Date timeToMatch, Date timeToCheck) {
		if (timeToMatch != Task.EMPTY_TIME) {
			if (timeToCheck == Task.EMPTY_TIME) {
				return false;
			}
			if (!timeToMatch.equals(timeToCheck)) {
				return false;
			}
		} else {
			if (timeToCheck != Task.EMPTY_TIME) {
				return false;
			}
		}

		return true;
	}

}
